/**
 * package.json --> extension.js --> openWebIDE.js
 * openWebIDE.js成了插件实际的入口文件
 */

const vscode = require('vscode');
const fs = require('fs');
const path = require('path');

const { launchStaticServer, bindWSServer } = require('./devServer');
const { isWin, info, errorInfo, EventSource, nomalizePath, promisify, checkPortUsed } = require('./utils.js');
const { getSavePointFilePath, openSavePointFileForFile } = require('./service/savePointFile.js');


/**
 * webview使用的文件所在的路径
 */
let webViewResPath;

/**
 * 获得ide入口页面的内容
 * @param {number} servicerPort
 * @param {{}} params
 *    附加参数
 * @returns 
 */
function getIDEEntranceContent(servicerPort, params) {
    const entranceHTML = path.join(webViewResPath, 'IDE-entrance.html');
    //文件系统直接读插件目录中的文件
    let html = fs.readFileSync(entranceHTML, 'utf-8');
    //带一些参数给ide端,为了能存在url中,使用escape
    const paramsStr = escape(JSON.stringify(params));
    //info(JSON.stringify(params),paramsStr);
    return html.replace(/\$\{servicerPort\}/g, servicerPort + '').replace('${params}', paramsStr);
}


const { workSpacePath, basePath, setExtensionPath } = require('./base.js')
/**
 * webIDE部署的相对路径
 */
const webviewSubPath = '/resources/webview/';


/**
 * 在插件初始化的时候,由extension.js调用
 * @param {vscode.ExtensionContext} context 
 */
module.exports = function (context) {
    if (!vscode.workspace.workspaceFolders) {
        vscode.window.showErrorMessage('当前工作目录为空，请在工作目录下重新打开vs code!');
    }


    /**
     * vscode插件内的资源加载路径
     */
    webViewResPath = path.join(context.extensionPath, webviewSubPath);
    //设置给base.js供其他文件使用
    setExtensionPath(webViewResPath);

    const { proxy } = require('./service/proxy.js');


    /**
     * 路径到panel的映射,已经关闭的handler会从这里移出
     */
    const panelHandler = (function () {
        const _map = new Map();
        return Object.assign(new EventSource(), {
            allPanelPaths() {
                //迭代器转数组
                return [..._map.keys()];
            },
            /**
             * 获取可见(但未必拥有输入焦点,即active)的panel路径
             */
            visiblePanelPaths() {
                let ps = [];
                for (let p of _map.keys()) {
                    const panel = _map.get(p);
                    panel.visible && ps.push(p);
                }
                ps.length === 0 && (ps = null)
                return ps;
            },
            /**
             * 处于激活状态的panel的path
             */
            activePanelPath: null,

            getPanel(relaPath) {
                return _map.get(relaPath);
            },

            setPanel(relaPath, panel) {
                _map.set(relaPath, panel);
            },

            deletePanel(relaPath) {
                _map.delete(relaPath);
            },

            /**
             * 激活panel,并获得输入焦点
             */
            async activePanel(relaPath) {
                //info(relaPath)
                if (relaPath) {
                    /**
                     * @type {vscode.WebviewPanel}
                     */
                    const panel = this.getPanel(relaPath);
                    if (panel) {
                        panel.reveal(vscode.ViewColumn.one);
                    }
                }
            }
        });
    })()

    /**
     * 创建服务对象
     */
    const services = require('./service/services.js')(context.subscriptions, panelHandler);

    /**
     * 当webview关闭时,清空status
     */
    panelHandler.addListener('change', () => {
        //当没有可见的panel时,tab没选中或者焦点在其他group的editor上
        if (!panelHandler.visiblePanelPaths()) {
            // @ts-ignore
            services.showStatusMsg(null);
        }
    });



    /**
   * 得到配置的服务端口
   */
    let servicerPort = vscode.workspace.getConfiguration().get('mxDev.servicePort') || 23331;
    const cProcess = require('child_process');

    (async () => {
        /**
         * 检测端口是否被占用,支持打开多个vscode程序
         */
        let isUse;
        do {
            try {
                isUse = await checkPortUsed(servicerPort);
            } catch (e) {
                info(e);
                isUse = true;
            }
            if (isUse) {
                info(`端口${servicerPort}可能被占用,尝试下一个端口`);
                servicerPort = servicerPort * 1 + 1;
            }
        } while (isUse);

        info('servicerPort', servicerPort);
        /**
         * 启动http服务器,对webide提供服务
         */
        const httpServer = launchStaticServer({ guard: 'webview', port: servicerPort, baseDirInFileSystem: webViewResPath, proxy });
        /**
         * 创建websocket服务
         */
        bindWSServer('service', httpServer, services);
        info("devServer启动完毕,端口:", servicerPort);
    })();


    /*---------------------------------------openWebIDE-------------------------------------- */
    const w = vscode.window;

    /**
     * 演示或使用目标文件路径打开设计器(IDE)
     * @param {vscode.Uri} uri 
     * 要设计的文件的路径(在vue下是.vue文件)
     */
    async function openWebIDE(uri) {
        let relaPath, title, blank = true;
        if (uri) {
            /**
             * @type {String}
             */
            let p = uri.path;
            p = nomalizePath(p);
            /**
             * 验证:要不是空文件,要不已经存在savePoint文件
             */
            const [fileSize, err] = await promisify(resolve => {
                fs.stat(p, (err, stats) => {
                    resolve(err ? null : stats.size, err);
                });
            });
            if (err) {
                w.showErrorMessage(`文件${p}不能访问!`);
                errorInfo(err);
                throw err;
            }

            info(p, fileSize);


            //得到相对于项目根目录的相对路径
            relaPath = path.relative(workSpacePath, p);

            /**
             * 目标文件非空的时候,必须存在保存点文件,可以通过添加假的保存点文件的方式绕过去
             */
            if (fileSize > 0) {
                const [dir, target] = getSavePointFilePath(relaPath);
                const indexFile = path.join(dir, target + '.index.json');
                //判断保存点文件中的索引文件是否存在
                const [, err] = await promisify(resolve => {
                    fs.stat(indexFile, (err, stats) => {
                        resolve(null, err);
                    });
                });
                //目标文件非空时,同时索引文件不存在
                if (err) {
                    w.showErrorMessage(`文件${p}非空，请从一个空文件开始进行设计!`);
                    return;
                }
            }
            /**
             * 打开索引文件,判断是否已经存在索引 
             */
            const spf = await openSavePointFileForFile(relaPath);
            /**
             * @type {Boolean} 
             *    blank为true表示没有过保存点
             */
            blank = Object.keys(spf.indexes).length === 0;

            //取文件名做webview的title
            title = relaPath.split(path.sep).pop() + '设计';
        } else {
            //默认值
            relaPath = Symbol.for('demo');
            title = 'demo';
        }
        /**
         * @type {vscode.WebviewPanel}
         */
        let panel = panelHandler.getPanel(relaPath);
        /**
         * 创建webview或者显示
         */
        if (!panel) {
            panel = vscode.window.createWebviewPanel(
                'mxdev.ide', // viewType
                title, // 视图标题
                vscode.ViewColumn.One, // 显示在编辑器的哪个部位
                {
                    enableScripts: true, // 启用JS，默认禁用
                    retainContextWhenHidden: true, // webview被隐藏时保持状态，避免被重置
                }
            );

            //设置webview内容,relaPath为symbol时会被序列化为undefined
            panel.webview.html = getIDEEntranceContent(servicerPort, { "target": relaPath, blank });
            //被用户手动关闭
            panel.onDidDispose(_ => {
                panelHandler.deletePanel(relaPath);
                /**
                 * 如果没有保存要自动保存,并给与提示(是否要保存)
                 */
                info(relaPath, '页面崩溃或关闭了');

                panelHandler.fireListener('change', 'destroy', relaPath);
            });

            /**
             * 只在panel改变active状态时改变,但panel第一次打开的时候不会触发
             */
            panel.onDidChangeViewState(e => {
                if (panel.active) {
                    //active时设置activePanelPath
                    panelHandler.activePanelPath = relaPath;
                } else {
                    //如果activePanelPath恰好等于自己,unactive时应该将activePanelPath置空
                    if (panelHandler.activePanelPath === relaPath) {
                        panelHandler.activePanelPath = null;
                    }
                }
                /**
                 * 在从一个panel切换到另一个panel的时候,会调用两次onDidChangeViewState,第一次取panelHandler.activePanelPath是undefined
                 */
                panelHandler.fireListener('change', 'active', relaPath);
            });

            panelHandler.setPanel(relaPath, panel);
            panelHandler.fireListener('change', 'create', panel);
        } else {
            /**
             * reveal相当于active方法
             */
            panel.reveal(vscode.ViewColumn.One);
        }

        return panel;
    }

    /**
     * 打开demo webview,不带参数
     */
    context.subscriptions.push(vscode.commands.registerCommand('mxdev.demo', openWebIDE));
    /**
     * 这个命令是带参数的,会将要被设计的文件的路径(相对于项目根路径)传进来
     */
    context.subscriptions.push(vscode.commands.registerCommand('mxdev.openAndDev', openWebIDE));

    return services;
};
